﻿/// <reference path="../scripts/typings/winjs/winjs.d.ts" />
/// <reference path="../scripts/typings/jquery/jquery.d.ts" />
/// <reference path="classes.d.ts" />
import ko = require("knockout");
import Project = require("Project");
import Track = require("Track");
import Selection = require("Selection");
import Clip = require("Clip");
import $ = require("jquery");
import StorageFolder = Windows.Storage.StorageFolder;

enum SelectedType {
    Unknown,
    Track,
    Clip,
}

function formatMsec(msec: number) {
    msec = msec | 0;
    var minutes: any = (msec / 60000) | 0;
    msec %= 60000;
    var seconds: any = (msec / 1000) | 0;
    var fraction: any = (msec % 1000);

    minutes = ((minutes < 10) ? "0" : "") + minutes;
    seconds = ((seconds < 10) ? "0" : "") + seconds;
    fraction = ((fraction < 10) ? "00" : ((fraction < 100) ? "0" : "")) + fraction;

    var time = `${minutes}:${seconds}.${fraction}`;
    return time;
}

(<any>ko.extenders).clamp = (target, range: [number, number]) => {
    ko.computed(() => {
        if (target() < range[0]) {
            target(range[0]);
        } else if (target() > range[1]) {
            target(range[1]);
        }
    });
}

class Editor {
    public constructor() {
        this.zoom = ko.observable(this.initialZoom).extend({ clamp: [this.maxZoom, this.minZoom] });
        this.cursor = ko.observable(0).extend({ clamp: [0, Infinity] });

        var project = this.project = ko.observable(new Project(this));
        this.selection = ko.observable(new Selection(this.project()));
        project.subscribe(() => { this.selection(new Selection(this.project())); });
        this.tracksWidth = ko.pureComputed(() => this.project().maxTrackLength() * this.zoom() + "px");
        this.trackHeightSum = ko.pureComputed(() => {
            var sum = 0;
            var tracks = this.project().tracks();
            for (let i = 0; i < tracks.length; i++) {
                sum += tracks[i].height();
            }
            return sum + 300 + "px";
        });
        this.settingsTransform = ko.pureComputed(() => {
            return (new MSCSSMatrix()).translate(0, -this.scrollPos().y);
        });
        this.playbackTimeString = ko.pureComputed(() => {
            var msec = project().playbackMsec();
            var time = formatMsec(msec);
            return time;
        });
        this.cursorTimeString = ko.pureComputed(() => {
            return formatMsec(this.cursor() / 10000);
        });
        this.playbackState = this.project().playbackState;

    }

    addMonoTrack(vm: any, x: any) {
        this.project().createTrack("MonoTrack");
    }

    addStereoTrack(vm: any, x: any) {
        this.project().createTrack("StereoTrack");
    }

    importTracks() {
        this.project().project.importTracksAsync(this.cursor());
    }

    playProject() {
        if (this.playbackState() == "Playing") {
            return;
        }
        this.project().project.play(this.cursor());
    }

    stopPlayback() {
        this.project().project.stop();
    }

    rewindCursor() {
        this.cursor(0);
    }

    exportProject() {
        this.project().project.exportAsync();
    }

    splitClip() {
        if (this.focusClip().isLoading()) {
            this.showError("Clips that are being loaded can't be split.");
            return;
        }
        try {
            this.focusClip().split(this.cursor());
        } catch (e) {
            this.showError("Can't split twice in the same spot.");
        }
    }

    showError(message: string, placement: string = "bottom") {
        this.errorMessage(message);
        this.errorFlyout().winControl.show(document.activeElement,placement);
    }
    
    onCanvasTap = (event: {offsetX: number, offsetY: number}) => {
        var x = event.offsetX + this.scrollPos().x - parseFloat($(this.canvasElem()).css("padding-left"));
        this.cursor(x / this.zoom());
    };

    onHold = (event, element) => {
        /**if (event.type === "MSGestureHold") {
            element.style.msPointerEvents = "none";
        }
        if (event.type === "MSGestureEnd") {
            element.style.msPointerEvents = "auto";
        }
        else if (event.type === "MSGestureChange") {
        }*/
    };

    onDragOver = (event) => {
        if (!this.dragClip()) {
            return;
        }

        if (event.targetTrack) {
            this.dragTargetTrack(event.targetTrack);
        }
        var zoom = this.zoom();

        var pointerTime =
            (
                event.clientX
                - this.canvasElem().querySelector('.tracks-wrapper').getBoundingClientRect().left
                )
            / zoom
            - this.dragGrabOffset();

        var canmove = this.dragClip().clip.canMoveTo(this.dragTargetTrack().track, pointerTime);

        if (canmove.canMove) {
            this.dragTargetTime(pointerTime);
            this.dragCanDrop(true);
        } else {
            if (canmove.suggestedOffset !== null) {
                // move to suggested time if within viewport
                var viewLeft = this.scrollPos().x;
                var viewRight = viewLeft + this.viewportElem().clientWidth;
                var clipLeft = canmove.suggestedOffset * zoom;
                var clipRight = clipLeft + this.dragClip().duration() * zoom;
                if (!(clipLeft > viewRight || clipRight < viewLeft)) {
                    this.dragTargetTime(canmove.suggestedOffset);
                    this.dragCanDrop(true);
                    return;
                }
            }

            this.dragTargetTime(pointerTime);
            this.dragCanDrop(false);
        }
    };

    deleteTrack() {
        if (this.focusTrack())
            this.focusTrack().delete();
    }

    deleteClip() {
        if (this.focusClip())
            this.focusClip().delete();
    }

    clipFadeIn() {
        this.applyClipEffect('fadeIn');
    }

    clipFadeOut() {
        this.applyClipEffect('fadeOut');
    }

    clipNormalize() {
        this.applyClipEffect('normalize');
    }

    applyClipEffect(effect) {
        if (this.focusClip()) {
            if (this.focusClip().isLoading()) {
                this.showError("Can't apply effects to a clip that's being loaded");
                return;
            }
            this.focusClip()[effect]();
        }
    }

    showOpenProjectTab = () => {
        this.topAppBar().winControl.hide();
        WinJS.UI.Animation.exitPage(this.tracksTab()).then(() => {
            this.tracksTab().classList.add("hidden");
            this.openProjectTab().classList.remove("hidden");
            WinJS.UI.Animation.enterPage(this.openProjectTab());
        });
    }

    showSaveProjectTab = () => {
        this.topAppBar().winControl.hide();
        WinJS.UI.Animation.exitPage(this.tracksTab()).then(() => {
            this.tracksTab().classList.add("hidden");
            this.saveProjectTab().classList.remove("hidden");
            WinJS.UI.Animation.enterPage(this.saveProjectTab());
        });
    }

    showTracksTab = () => {
        WinJS.Promise.join([
            WinJS.UI.Animation.exitPage(this.openProjectTab()),
            WinJS.UI.Animation.exitPage(this.saveProjectTab())
            ]).then(() => {
            this.openProjectTab().classList.add("hidden");
            this.saveProjectTab().classList.add("hidden");
            this.tracksTab().classList.remove("hidden");
            WinJS.UI.Animation.enterPage(this.tracksTab());
        });
    }

    showFlyoutApplyEffect = (self, event) => {
        this.flyoutApplyEffect().winControl.show(event.target, "bottom");
    }

    project: KnockoutObservable<Project>;
    cursor: KnockoutObservable<number>;
    focusTrack: KnockoutObservable<Track> = ko.observable(null);
    focusClip: KnockoutObservable<Clip> = ko.observable(null);
    selection: KnockoutObservable<Selection>;
    
    initialZoom = 1.0 / 250000;
    minZoom = 1.0 / 2000;
    maxZoom = 1.0 / 1000000;
    zoom: KnockoutObservable<number>;
    tracksWidth: KnockoutComputed<string>;
    trackHeightSum: KnockoutComputed<string>;
    editorElement: KnockoutObservable<any> = ko.observable();
    viewportElem: KnockoutObservable<HTMLElement> = ko.observable(null);
    scrollPos = ko.observable({x:0, y:0});
    settingsTransform: KnockoutComputed<MSCSSMatrix>;

    isZooming = ko.observable(false);


    unselectAll() {
        this.selection().setTime(0, 0).setTracks(0, 0);
    }

    canvasElem: KnockoutObservable<HTMLElement> = ko.observable(null);
    topAppBar: KnockoutObservable<any> = ko.observable(null);
    playbackState: KnockoutObservable<string>;
    playbackTimeString: KnockoutObservable<string>;
    cursorTimeString: KnockoutComputed<string>;
    openProjectTab: KnockoutObservable<HTMLElement> = ko.observable(null);
    saveProjectTab: KnockoutObservable<HTMLElement> = ko.observable(null);
    tracksTab: KnockoutObservable<HTMLElement> = ko.observable(null);
    flyoutApplyEffect: KnockoutObservable<HTMLElement> = ko.observable(null);

    errorMessage: KnockoutObservable<string> = ko.observable("");
    errorFlyout: KnockoutObservable<HTMLElement> = ko.observable(null);

    dragClip: KnockoutObservable<Clip> = ko.observable(null);
    dragTargetTrack: KnockoutObservable<Track> = ko.observable(null);
    dragTargetTime: KnockoutObservable<number> = ko.observable(0).extend({ clamp: [0, Infinity] });
    dragGrabOffset: KnockoutObservable<number> = ko.observable(0);
    dragCanDrop: KnockoutObservable<boolean> = ko.observable(true);
}

export = Editor